﻿
using System.IO;
using System.Text;
using Patterns4Net.Core.CecilExtensions;

namespace Patterns4Net.ArchitectureExplorer
{
    using System;
    using System.Reflection;
    using Infrastructure.Config;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Linq;
    using System.Threading;
    using System.Windows;
    using System.Windows.Threading;
    using System.Xml.Linq;
    using Caliburn.Micro;
    using Documentation;
    using Infrastructure;
    using ViewModels;
    using ViewModels.Messages;
    using ViewModels.Services;
    using Core;
    using Core.AbstractionLevel;
    using PatternEnforcer.Core;
    using AttributedModelServices = System.ComponentModel.Composition.AttributedModelServices;
    using Dispatcher = Infrastructure.Dispatcher;

    public class AppBootstrapper : Bootstrapper<ShellViewModel>
    {
        private CompositionContainer container;

        private IEventAggregator eventAggregator;

        protected override void Configure()
        {
            var mefCatalog = new AggregateCatalog(
                new AssemblyCatalog(typeof(PatternsLocator).Assembly),  // Patterns4Net.Core
                new AssemblyCatalog(typeof(PatternEnforcer).Assembly),  // Patterns4Net.PatternEnforcer.Core
                new AssemblyCatalog(typeof(ShellViewModel).Assembly));  // Patterns4Net.ArchitectureExplorer (this assembly)
            this.container = new CompositionContainer(mefCatalog);

            // Add configuration of components which are not configured using attributes
            var batch = new CompositionBatch();
            batch.AddExportedValue<IWindowManager>(new WindowManager());
            this.eventAggregator = new EventAggregator();
            batch.AddExportedValue<IEventAggregator>(this.eventAggregator);
            batch.AddExportedValue<ILog>(new FileLogger("log.txt"));
            this.container.Compose(batch);

            // Configuration of internal Caliburn logger location service
            LogManager.GetLog = type => new FileLogger("log." + type.Name + ".txt");            
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            if (e.Args.None(x => string.CompareOrdinal(x, "-log") == 0))
            {
                LogManager.GetLog = type => new NullLogger();
            }

            IPatternLevelProvider levelProvider = null;
            IPatternDocProvider docProvider = null;
            var errorMessages = new List<ErrorMessage>();
            try
            {
                var config = XDocument.Load("./Config/patterns.xml");
                levelProvider = ConfigPatternsLevelProvider.Create(config);
                docProvider = XmlPatternDocProvider.Create(config);
            }
            catch (Exception ex)
            {
                LogManager.GetLog(this.GetType()).Error(ex);
                errorMessages.Add(
                    new ErrorMessage(
                        "File 'Config/patterns.xml' cannot be loaded. Make sure that the file is not missing " +
                        "and contains valid xml that conforms patterns.xsd schema. However, the application loaded default " +
                        "'null' configuration and can start up.", 
                        true));
                levelProvider = new NullPatternsLevelProvider();
                docProvider = new NullPatternDocProvider();
            }

            var settings = new Settings();
            var serializer = this.container.GetExportedValue<ISettingsSerializer>();
            var settingsFound = true;
            string settingsFilename = "Config/config.xml";
            try
            {
                settings = serializer.Load(settingsFilename);
            }
            catch (Exception ex)
            {
                LogManager.GetLog(this.GetType()).Error(ex);
                settingsFound = false;
                errorMessages.Add(
                    new ErrorMessage(
                        "File 'Config/config.xml' cannot be loaded. Make sure that the file is not missing " +
                        "and contains valid xml that conforms architecture-explorer-config.xsd schema. " +
                        "However, the application loaded default 'null' configuration and can start up.",
                        true));                
            }

            var batch = new CompositionBatch();
            var errors = new StringBuilder();
            try
            {
                var enforcerConfig = Patterns4Net.PatternEnforcer.Core.Config.Config.Create(settingsFilename);
                CompositionContainerHelper.UpdateContainer(enforcerConfig, batch, errors);
            }
            catch (Exception ex)
            {
                if (settingsFound)
                {
                    errorMessages.Add(
                        new ErrorMessage(
                            "File 'Config/config.xml' cannot be loaded by Pattern Enforcer. Make sure that the file is not missing " +
                            "and contains valid xml that conforms pattern-enforcer.xsd schema. " +
                            "However, the application loaded default 'null' configuration and can start up.",
                            true));
                }
            }

            if (errors.Length > 0)
            {
                errorMessages.Add(new ErrorMessage(errors.ToString(), true));
            }
            
            batch.AddExportedValue<IPatternLevelProvider>(levelProvider);
            batch.AddExportedValue<IPatternDocProvider>(docProvider);
            batch.AddExportedValue<IDispatcher>(new Dispatcher(sender as DispatcherObject));
            batch.AddExportedValue(settings);
            batch.AddExportedValue("settingsFilename", settingsFilename);
            this.container.Compose(batch);

            base.OnStartup(sender, e);

            if (errorMessages.Any())
            {
                foreach (var message in errorMessages)
                {
                    this.eventAggregator.Publish(message);
                }
            }

            // parse xslt template in advance...
            var parser = this.container.GetExportedValue<DocumentationParser>();
            new Thread(parser.LoadTransform).Start();
        }

        protected override void OnUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            // We'll all that by hand, since we cannot trust Caliburn conventions 
            // when we are in such exceptional state...
            var message =
                string.Format(
                    "The application experienced an unexpected exception of type {0} and therefore will be closed. " + 
                    "To enable logging, run application with option '-log'. " +
                    "Exception Message: {1}",
                    e.Exception.GetType().Name,
                    e.Exception.Message);
            MessageBox.Show(message, "Unexpected Exception", MessageBoxButton.OK, MessageBoxImage.Error);
            Environment.Exit(1);
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if (exports.Count() != 1)
            {
                var message = "The application experienced an unexpected error and therefore will be closed. " +
                              "To enable logging, run application with option '-log'. " +
                              "Detailed message has been logged. ";
                MessageBox.Show(message, "Unexpected Error", MessageBoxButton.OK, MessageBoxImage.Error);

                string logMessage;
                if (exports.Count() > 1)
                {
                    logMessage = string.Format("More than one instance found for contract {0}.", contract);
                }
                else
                {
                    logMessage = string.Format("Could not locate any instances of contract {0}.", contract);
                }

                LogManager.GetLog(this.GetType()).Warn(logMessage);
                Environment.Exit(2);
            }

            return exports.First();
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);
        }  
    }
}
